package com.atguigu.tingshu.account.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private UserAccountDetailMapper userAccountDetailMapper;

	@Override
	public void saveUserAccount(Long userId) {
		UserAccount userAccount = new UserAccount();
		userAccount.setUserId(userId);
		this.save(userAccount);
	}

	/**
	 * 获取账户可用余额
	 *
	 * @param userId
	 * @return
	 */
	@Override
	public BigDecimal getAvailableAmount(Long userId) {
		//1.获取账户信息
		UserAccount userAccount = this.getUserAccount(userId);
		//2.获取可用余额
		if (userAccount != null) {
			return userAccount.getAvailableAmount();
		}
		return null;
	}

	/**
	 * 获取账户信息
	 *
	 * @param userId
	 * @return
	 */
	@Override
	public UserAccount getUserAccount(Long userId) {
		LambdaQueryWrapper<UserAccount> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(UserAccount::getUserId, userId);
		return userAccountMapper.selectOne(queryWrapper);
	}

	/**
	 * 检查及锁定账户余额，验证账户余额是否充足、将部分金额进行锁定
	 *
	 * @param accountLockVo 订单编号、用户ID、锁定金额、购买内容
	 * @param userId
	 * @return
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo, Long userId) {
		//1.幂等性处理：setnx避免同一笔订单多次对账户进行锁定，非首次处理查询Redis得到锁定结果，将锁定结果返回
		String key = RedisConstant.ACCOUNT_MUTIPLE_CHECK + accountLockVo.getOrderNo();
		String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + accountLockVo.getOrderNo();
		Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, accountLockVo.getOrderNo(), 1, TimeUnit.HOURS);
		if (!flag) {
			//说明非首次调用 尝试从Redis中获取账户锁定结果，如果有结果则直接返回
			AccountLockResultVo accountLockResultVo = (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
			if (accountLockResultVo == null) {
				//删除重复锁定key,抛出异常
				redisTemplate.delete(key);
				throw new GuiguException(ResultCodeEnum.ORDER_SUBMIT_REPEAT);
			}
			return accountLockResultVo;
		}

		//2.根据用户ID+锁定金额对账户表中记录进行查询（余额是否充足）利用数据库“锁”机制避免并发情况下对账户多次锁定-采用“悲观锁”-行锁
		UserAccount userAccount = userAccountMapper.check(userId, accountLockVo.getAmount());
		if (userAccount == null) {
			//锁定失败
			redisTemplate.delete(key);
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
		}

		//3.如果查询余额充足，执行锁定操作
		int cout = userAccountMapper.lock(userId, accountLockVo.getAmount());
		if (cout == 0) {
			//锁定失败
			redisTemplate.delete(key);
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
		}
		//4.检查锁定都成功，将锁定结果存入Redis，后续账户扣减、账户恢复都可以从Redis获取
		AccountLockResultVo accountLockResultVo = BeanUtil.copyProperties(accountLockVo, AccountLockResultVo.class);
		redisTemplate.opsForValue().set(lockResultKey, accountLockResultVo, 1, TimeUnit.HOURS);

		//5. 新增账户变动日志
		this.saveUserAccountDetail(accountLockVo.getUserId(), "锁定：" + accountLockVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_LOCK, accountLockVo.getAmount(), accountLockVo.getOrderNo());

		//5.将锁定结果返回
		return accountLockResultVo;
	}

	/**
	 * 保存账户变动日志记录
	 *
	 * @param userId
	 * @param s
	 * @param accountTradeTypeLock
	 * @param amount
	 * @param orderNo
	 */
	@Override
	public void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
		UserAccountDetail userAccountDetail = new UserAccountDetail();
		userAccountDetail.setUserId(userId);
		userAccountDetail.setTitle(title);
		userAccountDetail.setTradeType(tradeType);
		userAccountDetail.setAmount(amount);
		userAccountDetail.setOrderNo(orderNo);
		userAccountDetailMapper.insert(userAccountDetail);
	}

	/**
	 * 扣减账户锁定金额
	 *
	 * @param orderNo
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void minus(String orderNo) {
		//1.业务去重避免多次扣减
		String key = "account:minus" + orderNo;
		Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
		if (flag) {
			//2.扣减锁定金额
			//2.1 从Redis中获取锁定结果对象
			String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + orderNo;
			AccountLockResultVo accountLockResultVo = (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
			if (accountLockResultVo != null) {
				//2.2 扣减锁定金额
				int count = userAccountMapper.minus(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
				if (count == 0) {
					//解除去重
					redisTemplate.delete(key);
					throw new GuiguException(ResultCodeEnum.LOGIN_AUTH.ACCOUNT_MINUSLOCK_ERROR);
				}
				//2.3 记录账户变动日志
				this.saveUserAccountDetail(accountLockResultVo.getUserId(), accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_MINUS, accountLockResultVo.getAmount(), orderNo);

				//2.4 删除Redis中锁定结果
				redisTemplate.delete(lockResultKey);

			}
		}
	}

	/**
	 * 还原锁定金额
	 *
	 * @param orderNo
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void unlock(String orderNo) {
		//1.业务去重避免多次扣减
		String key = "account:plus" + orderNo;
		Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
		if (!flag) {
			return;
		}
		//2.从Redis中获取锁定结果用于恢复锁定金额
		String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + orderNo;
		AccountLockResultVo accountLockResultVo = (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
		if (accountLockResultVo == null) {
			return;
		}
		int count = userAccountMapper.unLock(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
		if (count == 0) {
			//更新失败 解除去重
			redisTemplate.delete(key);
			throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR);
		}
		//3.记录账户变动日志
		this.saveUserAccountDetail(accountLockResultVo.getUserId(), "解锁：" + accountLockResultVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK, accountLockResultVo.getAmount(), orderNo);
		//4.恢复锁定金额后，删除锁定结果，以防重复恢复
		redisTemplate.delete(lockResultKey);
	}

	/**
	 * 为指定账户充值
	 * @param userId
	 * @param rechargeAmount
	 */
	@Override
	public void add(Long userId, BigDecimal rechargeAmount) {
		userAccountMapper.add(userId, rechargeAmount);
	}

	@Override
	public IPage<UserAccountDetail> getUserRechargePage(Page<UserAccountDetail> pageParam, Long userId) {
		// 调用mapper 方法
		return userAccountDetailMapper.getUserRechargePage(pageParam,userId);
	}

	@Override
	public IPage<UserAccountDetail> getUserConsumePage(Page<UserAccountDetail> pageParam, Long userId) {
		// 调用mapper 层方法
		return userAccountDetailMapper.getUserConsumePage(pageParam, userId);
	}

}
